<?php
/*
 * @Descripttion: 系统助手函数库以及系统常量定义
 * @version:
 * @Author: ewgof
 * @Date: 2020-08-11 21:40:46
 * @LastEditors: ewgof
 * @LastEditTime: 2020-10-19 12:49:35
 */

use cw\facade\Config;
use cw\facade\Lang;
use cw\facade\Route;
use cw\facade\Db;
use cw\facade\Cache;
use Workerman\Protocols\Http\Response;

// 系统常量定义
const VERSION = '0.0.1';
const __APP__ = __DIR__ . '/../application/';
const __CONFIG__ = __DIR__ . '/../config/';
const __LANG__ = __DIR__ . '/../lang/';
const __ROUTE__ = __DIR__ . '/../route/';
const __CW__ = __DIR__ . '/../cw/';
const __RUNTIME__ = __DIR__ . '/../runtime/';
const __ROOT__ = __DIR__ . '/../';


// 定义缓存驱动注册信息
const CW_CACHE_REGISTER = [
    'file' => \cw\FileCache::class,
    'memory' => \cw\MemoryCache::class,
    'redis' => \cw\RedisCache::class,
];


if (!function_exists('api_return_json')) {
    /**
     * @name: api_json_return
     * @msg: 接口统一返回函数
     * @param $code integer 操作错误代码
     * @param $msg string  提示信息
     * @param $data array 接口返回数据
     * @return:
     */
    function api_json_return($code = 0, String $msg = '', array $data = array())
    {
        if (!is_numeric($code)) {
            $code = 1;
        }
        $notice = lang($code);

        if (config('app.app_debug')) {
            // debug 开发 环境
            $msg = !empty($msg) ? $msg : $notice;
        } else {
            // 生产环境
            $msg = ($code === 0) ? $notice : lang(1);
        }
        $res = array(
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        );
        return json_encode($res);
    }
}


if (!function_exists('log_msg')) {
    /**
     * @name: log_msg
     * @msg: 写日志函数
     * @param $msg string 日志内容
     * @param $level string 日志级别（类型）
     * @param $filename string 日志文件名,如果需要独立记录日志,传递此参数,否则使用配置中的默认文件名
     * @return:
     */
    function log_msg(String $msg, String $level = 'info', $filename = null)
    {
        $msg = ' --> ['.$level.'] '.date('Y-m-d H:i:s').'|'.$msg.PHP_EOL;
        $path = rtrim(config('log.path'), '/') . '/';
        $path = $path . date('Ym') . '/';
        if (!file_exists($path)) {
            mkdir($path, 0755, true);
        }
        if (!$filename) {
            $filename = $path . date('d') . '_' . config('log.filename');
        } else {
            $filename = $path . date('d') . '_' . $filename;
        }
        file_put_contents($filename, $msg, FILE_APPEND);
    }
}


if (!function_exists('redis')) {
    // 初始化redis连接
    function redis()
    {
        $redis = new \Redis();
        $redis_config = config('redis');
        // var_dump($redis_config);return ;

        $redis->pconnect(
            $redis_config['host'],
            $redis_config['port'],
            $redis_config['timeout']
        );
        // $redis->connect('127.0.0.1', 6379, 60);
        if (!empty($redis_config['passwd'])) {
            $redis->auth($redis_config['passwd']);
        }
        if (!empty($redis_config['dbname'])) {
            $redis->select($redis_config['dbname']);
        } else {
            $redis->select(0);
        }
        return $redis;
    }
}


if (!function_exists('config')) {
    /**
     * @name: config
     * @msg: 获取或者设置参数的值,多个级别之间用 '.' 分隔;
     *       获取(不传递第二个参数):只传递 name 参数获取单一的配置值(返回 string),不传递参数获取全部的配置(返回 array)
     *       动态设置（传递第二个参数):最多支持5个级别的动态设置，再多级别的设置请直接修改配置文件。
     * @param string $name  配置参数的名称，多个级别之间用 '.' 分隔
     * @param string $val  配置参数的值，如果传入此参数说明是设置配置参数值
     * @return mixed 如果是获取操作则返回所获取的配置参数的值，如果是设置操作则返回一个布尔值：设置成功返回true，否则返回false；
    */
    function config(String $name = '', String $val = '')
    {
        if (empty($val)) {
            return Config::get($name);
        } else {
            return Config::set($name, $val);
        }
    }
}


if (!function_exists('lang')) {
    /**
     * @name: lang
     * @msg: 获取或者设置语言包的值;
     *       获取:只传递 name 参数
     *       动态设置:传递 name 参数和 val 参数
     * @param string name 语言包文件内定义的 key
     * @param string val
     * @return mixed 获取成功返回 string,设置失败返回 boolean
     */
    function lang(String $name, String $val = null)
    {
        if (empty($val)) {
            return Lang::get($name);
        } else {
            return Lang::set($name, $val);
        }
    }
}


if (!function_exists('route')) {
    /**
     * @name: route
     * @msg: 解析请求路由
     * @param object $connection  当前连接对象
     * @param object $request   当前请求对象
     * @return void
     */
    function route($connection, $data)
    {
        Route::dispatch($connection, $data);
    }
}


if (!function_exists('send')) {
    /**
     * @name: send
     * @msg: 给客户端发送响应（http）服务，已经配置了跨域，所以如果不是http请求，如果不需要跨域，请直接使用 connection 对象的 send（） 方法
     * @param object $connection 连接对象
     * @param mixed  $body 响应主体
     * @param integer  $code 响应状态码
     * @param array  $headers 响应头
     * @return void
     */
    function send($connection, $body = '', $code = 200, $headers = [])
    {
        $headers = array_merge([
            'Content-Type' => 'application/json;charset=utf-8',
            'Access-Control-Allow-Origin' => '*',
            'Access-Control-Allow-Headers' => 'token,Origin,X-Requested-With,Content-Type,Accept,rtime',
            'Access-Control-Allow-Methods' => 'GET,POST,PATCH,PUT,DELETE,OPTIONS',
        ], $headers);
        $response = new Response(200, $headers, $body);
        $connection->send($response);
    }
}




// if (!function_exists('packege_decode')) {
//     // 十六进制字符串解包为十进制数组
//     function package_decode($str)
//     {
//         return unpack('C*', $str);
//     }
// }

// if (!function_exists('packge_encode')) {
//     // 十进制数组打包为十六进制字符串
//     function package_encode(array $arr)
//     {
//         $pack = '';
//         foreach ($arr as $d) {
//             $pack .= pack("C*", $d);
//         }
//         return $pack;
//     }
// }


if (!function_exists('db_connect')) {
    // 初始化数据库连接
    function db_connect()
    {
        // var_dump('config all: ', config());
        $default_db = config('app.default_db');
        // var_dump('default_db: ', $default_db);return ;
        return Db::$default_db();
    }
}


if (!function_exists('validate')) {
    /**
     * @name: validate
     * @msg: 数据验证助手函数
     * @param string $class 验证器类,带完整的命名空间;如果区分验证场景,在类名后用点号'.'连接场景名,例如: \app\common\validate\UserValidate.create
     * @param Array $data 需要验证的数据
     * @param boolean $is_continue 验证遇到错误是否继续验证,默认否:false
     * @return {type}
     */
    function validate(String $class, array $data, $is_continue = false)
    {
        $obj = explode('.', $class);
        if (!class_exists($obj[0])) {
            return 'class not fund ' . $obj[0];
        }
        if (empty($obj[1])) {
            // 非场景验证
            $v = $obj[0] :: check($data);
        } else {
            // 场景验证
            $v = $obj[0]::make($data)->atScene($obj[1])->validate();
        }
        if ($v->isFail()) {
            if ($is_continue) {
                return $v->getErrors();
            } else {
                return $v->firstError();
            }
        } else {
            return true;
        }
    }
}


if (!function_exists('cache')) {
    /**
     * @name: cache
     * @msg: 设置/获取缓存值的助手函数
     * @param string $name 缓存 key
     * @param void $val 缓存值
     * @param integer $timeout 缓存有效期
     * @return boolean
     */
    function cache(String $name, $val = null, $timeout = null)
    {
        if (empty($name)) {
            return false;
        }
        if (!empty($val)) {
            return Cache::set($name, $val, $timeout);
        } else {
            return Cache::get($name);
        }
    }
}


if (!function_exists('make_sha')) {
    /**
     * @name: make_sha
     * @msg: 给字符串生成一个 12 位的 sha 值，只要传进来的字符串一样，出来的 sha 就一样
     * @param string $str 缓存的键名
     * @return string
     */
    function make_sha(String $str)
    {
        $len = mb_strlen($str) * 22;
        $str .= '_' . $len;
        $sha = md5($str);
        $key = substr($sha, 5, 16);
        return $key;
    }
}


if (!function_exists('make_clientid')) {
    /**
     * @name: make_clientid
     * @msg: 生成全局唯一性连接 id
     * @param integer $id connection 对象中的id
     * @return string
     */
    function make_clientid($id)
    {
        $str = mt_rand(111, 99999) . microtime(true) . uniqid() . $id;
        return make_sha($str);
    }
}


if (!function_exists('humansize')) {
    /**
     * 把以字节数表示的文件大小转换为 KB、MB等的人性化表示方式
     * @method humansize
     * @param  integer|float    $size             文件大小（字节数）
     * @param  boolean   $is_1024_multiple 转换比率是否为 1024 倍，默认true（1024倍），false（1000倍）
     * @param  boolean   $is_keep_suffix   返回值是否带上单位后缀，默认false（不带单位），true（带单位后缀）
     * @param  boolean    $is_use_universality_ext   返回值所带的单位是否使用普适性的表示：即用 KB 表示 KB 和 KiB 单位，true表示使用普适性单位（默认），false表示不使用普适性单位
     * @return boolean|float|string     本算法默认只支持到 YB/YiB 级别的大小，如果传入的字节数
     * 小于0或者大于YB/YiB级别，返回false；如果 $is_keep_suffix = true，返回的结果后带上单位后缀（string），否则返回计算后的结果 （float）
     */
    function humansize($size, $is_keep_suffix = false, $is_use_universality_ext = true, $is_1024_multiple = true)
    {
        if (empty($size) && $size !== 0) {
            return false;
        }
        // 单位后缀
        $suffix = array(
            1024 => array('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'),
            1000 => array('KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'),
        );

        if ($is_1024_multiple) {
            $multiple = 1024;
        } else {
            $multiple = 1000;
        }
        // 可以计算的最大字节数
        $max_limit = pow($multiple, count($suffix[$multiple]));

        if ($size > $max_limit) {
            return false;
        }

        if ($is_use_universality_ext) {
            $rst = $suffix[1000];
        } else {
            $rst = $suffix[$multiple];
        }
        foreach ($rst as $m) {
            $size = $size / $multiple;
            if ($size < $multiple) {
                $size = number_format($size, 1);
                if ($is_keep_suffix) {
                    return $size.' '.$m;
                } else {
                    return $size;
                }
            }
        }
    }
}
